/**
 *
 * This class represents the true engine of the AI.
 * It's supposed to find a optimal move to every game situation.
 *
 */
package AIEngine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;

public class AlphaBetaRandomEngine implements AIEngine {

	private int maxDepth;
	private int limitAlphaBeta;

	/**
	 * Constructor for the alphabeataengie by commiting the playerside
	 *
	 * @param black true = firstplayer, false = secondplayer.
	 */
	public AlphaBetaRandomEngine(boolean black, int iaLevel) {
		maxDepth = iaLevel;
		limitAlphaBeta = 4;
	}

	/**
	 * Checks if the search is supposed to cancel.
	 *
	 * @param node current position
	 * @return <b>true</b> canceling search, <b>false</b> going on search
	 */
	public boolean cutOff(Node node) {

		if(node.getTurnsLeft() <= limitAlphaBeta)
			maxDepth = limitAlphaBeta;

		if (node.getDepth() >= maxDepth) {
			return true;
		}

		return false;

	}

	/**
	 * getNodes detects all sequence positions for a node
	 *
	 * @see Node
	 * @param node root node, hear starts the search
	 * @return childslist a linkedlist filled with all sequence positions for the root node
	 */
	public LinkedList getNodes(Node node) {
		LinkedList<Node> childrenList = new LinkedList();
    	int[][] workplace = new int[6][6];
    	int[][] tempBoard = new int[6][6];
    	int marble = 0;
    	
    	if(node.getCamp()){
    		marble = -1;
    	}
    	else{
    		marble = 1;
    	}
    	
        if (BasicAI.checkWin(node.getBoard(), true) == true) {
            return null;
        }
        if (BasicAI.checkWin(node.getBoard(), false) == true) {
            return null;
        }

        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 6; j++) {
                if (BasicAI.isEmpty(node.getBoard()[i][j])) {
                	
                	BasicAI.fill(node.getBoard(), tempBoard);
                	tempBoard[i][j] = marble;
                	
                	workplace = BasicAI.rotate(tempBoard, 1, 1);
                    childrenList.add(new Node(node, new int[]{i, j, 1, 1}, workplace, node.getTurnsLeft()));

                    workplace = BasicAI.rotate(tempBoard, 1, -1);
                    childrenList.add(new Node(node, new int[]{i, j, 1, -1}, workplace, node.getTurnsLeft()));

                    workplace = BasicAI.rotate(tempBoard, 2, 1);
                    childrenList.add(new Node(node, new int[]{i, j, 2, 1}, workplace, node.getTurnsLeft()));

                    workplace = BasicAI.rotate(tempBoard, 2, -1);
                    childrenList.add(new Node(node, new int[]{i, j, 2, -1}, workplace, node.getTurnsLeft()));
                    
                    workplace = BasicAI.rotate(tempBoard, 3, 1);
                    childrenList.add(new Node(node, new int[]{i, j, 3, 1}, workplace, node.getTurnsLeft()));
                    
                    workplace = BasicAI.rotate(tempBoard, 3, -1);
                    childrenList.add(new Node(node, new int[]{i, j, 3, -1}, workplace, node.getTurnsLeft()));
                    
                    workplace = BasicAI.rotate(tempBoard, 4, 1);
                    childrenList.add(new Node(node, new int[]{i, j, 4, 1}, workplace, node.getTurnsLeft()));
                    
                    workplace = BasicAI.rotate(tempBoard, 4, -1);
                    childrenList.add(new Node(node, new int[]{i, j, 4, -1}, workplace, node.getTurnsLeft()));

                }
            }
        }
        
        if(childrenList.isEmpty())
        	return null;

        return childrenList;
	}

	/**
	 * Takes a node and evaluates the given position from it.
	 * 
	 * BasicAI.checkWin
	 * BasicAI.fullJudge
	 * @param node
	 * @return value
	 */
	public long evalNode(Node node) {
		long tmpValue = 0;

		tmpValue = BasicAI.fullJudge(node.getBoard(), node.getCamp(), node.getDepth(), node.getTurnsLeft(), limitAlphaBeta);
		node.setValue(tmpValue);

		return tmpValue;

	}

	/**
	 * Setter for the depth
	 * @param depth new value of depth
	 */
	public void setDepth(int depth) {
		this.maxDepth = depth;
	}

	/**
	 * Emits the best next possible move, deppending on the root node
	 *
	 * @see Node
	 * @param node root for the calculation of the worst following move
	 * @param alpha limit for the best possible move
	 * @param beta limit for the best opponent move
	 * @return next best move
	 */
	public long maxNode(Node node, long alpha, long beta) {
		LinkedList children;
		ListIterator iterator;
		Node tmpNode;
		long v = Long.MIN_VALUE;
		long value;

		if (cutOff(node)) {
			return evalNode(node);
		}

		children = getNodes(node);

		if (children == null) {
			return evalNode(node);
		}

		iterator = children.listIterator(0);
		while (iterator.hasNext()) {
			tmpNode = (Node) iterator.next();

			value = minNode(tmpNode, alpha, beta);

			if (value > v) {
				v = value;
			}

			if (v > beta) {
				node.setDepth(node.getDepth()+1);
				return v;
			}

			if(v > alpha){
				alpha = v;
			}  	
		}
		node.setDepth(node.getDepth()+1);

		return v;
	}

	/**
	 * Emits the worst next possible move, deppending on the root node
	 *
	 * @see Node
	 * @param node root for the calculation of the worst following move
	 * @param alpha limit for the best possible move
	 * @param beta limit for the best opponent move
	 * @return worst next possible move
	 */
	public long minNode(Node node, long alpha, long beta) {
		LinkedList children;
		ListIterator iterator;
		Node tmpNode;
		long v = Long.MAX_VALUE;
		long value;

		if (cutOff(node)) {
			return evalNode(node);
		}

		children = getNodes(node);

		if (children == null) {
			return evalNode(node);
		}

		iterator = children.listIterator(0);
		while (iterator.hasNext()) {
			tmpNode = (Node) iterator.next();

			value = maxNode(tmpNode, alpha, beta);

			if (value < v) {
				v = value;
			}

			if (v < alpha) {
				node.setDepth(node.getDepth()+1);
				return v;
			}

			if(v < beta){
				beta = v;
			}  	
		}
		node.setDepth(node.getDepth()+1);

		return v;

	}

	/**
	 * Searchs with the alpha-beta pruning a preferably and maybe best move.
	 * Depending on the searchdepth and the timelimit
	 *
	 * @param node basic position for the serach
	 * @return Node, the best move is returned as a node and <b>null</b> will
	 * be returned if ther is no next move or we dont findet it
	 */
	public Node getBestMove(Node node) {
		LinkedList children;
		ListIterator iterator;
		Node tmpNode;
		Node bestNode = null;
		long alpha = Long.MIN_VALUE;
		long beta = Long.MAX_VALUE;
		ArrayList<Node> bestMovesArray = new ArrayList<Node>();
		Random randomIndex = new Random(System.nanoTime());
		int index;

		children = getNodes(node);
		
		if (children == null) {
			return null;
		}

		iterator = children.listIterator(0);
		while (iterator.hasNext()) {
			tmpNode = (Node) iterator.next();
			tmpNode.setValue(minNode(tmpNode, alpha, beta));

			if (tmpNode.getValue() >= alpha) {
				if (tmpNode.getValue() > alpha) {
					alpha = tmpNode.getValue();
					bestNode = tmpNode;
					
					bestMovesArray.clear();
					bestMovesArray.add(bestNode);
				}
				if (tmpNode.getValue() == alpha && tmpNode.getDepth() < bestNode.getDepth()) {
					alpha = tmpNode.getValue();
					bestNode = tmpNode;
					
					bestMovesArray.clear();
					bestMovesArray.add(bestNode);
				}else if (tmpNode.getValue() == alpha && (tmpNode.getDepth()==bestNode.getDepth())) {
					bestMovesArray.add(tmpNode);
				}
			}
		}

		Collections.shuffle(bestMovesArray);
		index = randomIndex.nextInt(bestMovesArray.size());
		bestNode = bestMovesArray.get(index);
		bestNode.setDepth(0);

		return bestNode;

	}
}

